package com.ollobot.authorization.service;

import com.ollobot.authorization.dto.TokenResponse;
import com.ollobot.authorization.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.crypto.SecretKey;
import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class JwtTokenService {

    @Value("${app.jwt.secret:ollobot-authorization-server-secret-key-2024}")
    private String jwtSecret;

    @Value("${app.jwt.access-token-expiration:3600}") // 1小时
    private long accessTokenExpiration;

    @Value("${app.jwt.refresh-token-expiration:2592000}") // 30天
    private long refreshTokenExpiration;

    private final RedisTemplate<String, Object> redisTemplate;

    public JwtTokenService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    private SecretKey getSigningKey() {
        return Keys.hmacShaKeyFor(jwtSecret.getBytes());
    }

    /**
     * 生成访问令牌
     */
    public String generateAccessToken(User user) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", user.getId());
        claims.put("email", user.getEmail());
        claims.put("username", user.getUsername());
        claims.put("provider", user.getProvider().name());
        claims.put("emailVerified", user.getEmailVerified());
        claims.put("firstName", user.getFirstName());
        claims.put("lastName", user.getLastName());
        claims.put("avatarUrl", user.getAvatarUrl());
        claims.put("tokenType", "access_token");

        return createToken(claims, user.getEmail(), accessTokenExpiration);
    }

    /**
     * 生成刷新令牌
     */
    public String generateRefreshToken(User user) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", user.getId());
        claims.put("email", user.getEmail());
        claims.put("tokenType", "refresh_token");

        String refreshToken = createToken(claims, user.getEmail(), refreshTokenExpiration);
        
        // 将刷新令牌存储到Redis
        String redisKey = "refresh_token:" + user.getId();
        redisTemplate.opsForValue().set(redisKey, refreshToken, refreshTokenExpiration, TimeUnit.SECONDS);
        
        return refreshToken;
    }

    /**
     * 创建JWT令牌
     */
    private String createToken(Map<String, Object> claims, String subject, long expiration) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration * 1000);

        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(getSigningKey(), SignatureAlgorithm.HS512)
                .compact();
    }

    /**
     * 从令牌中获取用户邮箱
     */
    public String getEmailFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.getSubject();
    }

    /**
     * 从令牌中获取用户ID
     */
    public Long getUserIdFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.get("userId", Long.class);
    }

    /**
     * 从令牌中获取所有声明
     */
    public Claims getClaimsFromToken(String token) {
        return Jwts.parser()
                .verifyWith(getSigningKey())
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }

    /**
     * 验证令牌是否有效
     */
    public boolean validateToken(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            return !isTokenExpired(claims);
        } catch (Exception e) {
            log.error("JWT token validation failed: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 检查令牌是否过期
     */
    private boolean isTokenExpired(Claims claims) {
        Date expiration = claims.getExpiration();
        return expiration.before(new Date());
    }

    /**
     * 刷新访问令牌
     */
    public String refreshAccessToken(String refreshToken, User user) {
        if (!validateToken(refreshToken)) {
            throw new RuntimeException("Invalid refresh token");
        }

        Claims claims = getClaimsFromToken(refreshToken);
        String tokenType = claims.get("tokenType", String.class);
        
        if (!"refresh_token".equals(tokenType)) {
            throw new RuntimeException("Invalid token type");
        }

        // 检查Redis中是否存在该刷新令牌
        String redisKey = "refresh_token:" + user.getId();
        String storedToken = (String) redisTemplate.opsForValue().get(redisKey);
        
        if (!refreshToken.equals(storedToken)) {
            throw new RuntimeException("Refresh token not found or expired");
        }

        return generateAccessToken(user);
    }

    /**
     * 撤销刷新令牌
     */
    public void revokeRefreshToken(Long userId) {
        String redisKey = "refresh_token:" + userId;
        redisTemplate.delete(redisKey);
    }

    /**
     * 生成令牌对（访问令牌和刷新令牌）
     */
    public TokenResponse generateTokenPair(User user) {
        String accessToken = generateAccessToken(user);
        String refreshToken = generateRefreshToken(user);

        // 计算过期时间戳（毫秒）
        long expiresAt = System.currentTimeMillis() + accessTokenExpiration * 1000;

        TokenResponse response = TokenResponse.builder()
                .accessToken(accessToken)
                .refreshToken(refreshToken)
                .tokenType("Bearer")
                .expiresIn(accessTokenExpiration)
                .expiresAt(expiresAt)
                .scope("read write")
                .build();

        return response;
    }

    /**
     * 获取token的详细信息（用于验证接口）
     */
    public Map<String, Object> getTokenDetails(String token) {
        Map<String, Object> details = new HashMap<>();
        
        try {
            Claims claims = getClaimsFromToken(token);
            
            details.put("valid", !isTokenExpired(claims));
            details.put("userId", claims.get("userId", Long.class));
            details.put("email", claims.getSubject());
            details.put("username", claims.get("username", String.class));
            details.put("provider", claims.get("provider", String.class));
            details.put("emailVerified", claims.get("emailVerified", Boolean.class));
            details.put("tokenType", claims.get("tokenType", String.class));
            details.put("expiresAt", claims.getExpiration().getTime());
            details.put("firstName", claims.get("firstName", String.class));
            details.put("lastName", claims.get("lastName", String.class));
            details.put("avatarUrl", claims.get("avatarUrl", String.class));
            
        } catch (Exception e) {
            log.error("Failed to parse token details: {}", e.getMessage());
            details.put("valid", false);
            details.put("errorMessage", e.getMessage());
        }
        
        return details;
    }
}
